import bpy
import os
from bpy.types import Panel, Operator, PropertyGroup, UIList
from bpy.props import StringProperty, IntProperty, FloatProperty, CollectionProperty, PointerProperty, BoolProperty
from bpy_extras.io_utils import ImportHelper

# 材质设置属性组
class MaterialSettings(PropertyGroup):
    name: StringProperty(
        name="材质名称",
    )# type: ignore
    image_path: StringProperty(name="图片路径", subtype='FILE_PATH')# type: ignore
    rows: IntProperty(
        name="行数", 
        default=0, 
        min=0, 
        max=30,
        update=lambda self, context: self.update_mapping(context)
    )# type: ignore
    columns: IntProperty(
        name="列数", 
        default=0, 
        min=0, 
        max=30,
        update=lambda self, context: self.update_mapping(context)
    )# type: ignore
    offset_x: FloatProperty(
        name="列间距", 
        default=0.0,
        step=0.001,
        min=-100.0, 
        max=100.0,
        precision=5,
        update=lambda self, context: self.update_mapping(context)
    ) # type: ignore
    offset_y: FloatProperty(
        name="行间距", 
        default=0.0, 
        step=0.001,
        min=-100.0, 
        max=100.0,
        precision=5,
        update=lambda self, context: self.update_mapping(context)
    )# type: ignore
    start_x: FloatProperty(
        name="X起始点", 
        default=0.0, 
        step=0.001,
        min=-100.0, 
        max=100.0,
        precision=5,
        update=lambda self, context: self.update_mapping(context)
    )# type: ignore
    start_y: FloatProperty(
        name="Y起始点", 
        default=0.0, 
        step=0.001,
        min=-100.0, 
        max=100.0,
        precision=5,
        update=lambda self, context: self.update_mapping(context)
    )# type: ignore
    
    def update_mapping(self, context):
        """更新映射节点设置"""
        if self.name in bpy.data.materials:
            
            mat = bpy.data.materials[self.name]
            if mat.use_nodes:
                # 查找映射节点
                mapping_node = None
                for node in mat.node_tree.nodes:
                    if node.type == 'MAPPING':
                        mapping_node = node
                        break
                
                if mapping_node:
                    # 更新映射节点的位置
                    mapping_node.inputs['Location'].default_value[0] = (
                        self.start_x + self.columns * self.offset_x
                    )
                    mapping_node.inputs['Location'].default_value[1] = (
                        self.start_y + self.rows * self.offset_y
                    )

# 场景属性组存储所有材质设置
class SceneSettings(PropertyGroup):
    material_settings: CollectionProperty(type=MaterialSettings)# type: ignore
    active_index: IntProperty(default=-1)# type: ignore

# 选择图片操作符
class MATERIAL_OT_select_image(Operator, ImportHelper):
    bl_idname = "material.select_image"
    bl_label = "创建位置变换"
    bl_description = "创建位置变换表情切换的材质"
    
    filter_glob: StringProperty(
        default='*.jpg;*.jpeg;*.png;*.tif;*.tiff;*.bmp',
        options={'HIDDEN'}
    )# type: ignore
    
    
    name: StringProperty(
        default="XY_E",
        options={'HIDDEN'}
    )# type: ignore
    
    def execute(self, context):
        # 获取当前场景设置
        scene_settings = context.scene.scene_settings
        
        # 创建新的材质设置
        new_setting = scene_settings.material_settings.add()
        new_setting.name = self.name
        new_setting.image_path = self.filepath
        
        # 设置为活动项
        scene_settings.active_index = len(scene_settings.material_settings) - 1
        if self.name in [me.name for me in bpy.data.materials]:
            bpy.data.materials.remove(bpy.data.materials[self.name])
        # 创建材质并应用到选中的物体
        if not self.create_and_apply_material(context, new_setting):
            scene_settings.material_settings.remove(scene_settings.active_index)
            scene_settings.active_index=-1
            self.report({'ERROR'}, "导入图片失败")
            return {'CANCELLED'}
        self.name="XY_E"
        
        return {'FINISHED'}

    def invoke(self, context, event):
        count = 0
        while self.name in [me.name for me in bpy.data.materials]:
            self.name= f"XY_E_{count}"
            count += 1
        self.filepath=""
        return super().invoke(context, event)
    
    def create_and_apply_material(self, context, material_setting):
        """创建材质并应用到选中的物体"""
        # 创建材质
        mat = bpy.data.materials.new(name=material_setting.name)
        mat.use_nodes = True
        if not self.setup_material_nodes(mat, material_setting):
            bpy.data.materials.remove(mat)
            return False
        
        # 应用到选中的网格物体
        selected_objects = [obj for obj in context.selected_objects if obj.type == 'MESH']
        for obj in selected_objects:
            if obj.data.materials:
                obj.data.materials[0] = mat
            else:
                obj.data.materials.append(mat)
        return True
    
    def setup_material_nodes(self, mat, material_setting):
        """设置材质节点"""
        # 清除现有节点
        mat.node_tree.nodes.clear()
        
        # 创建节点
        nodes = mat.node_tree.nodes
        links = mat.node_tree.links
        
        # 创建原理化BSDF节点
        bsdf = nodes.new("ShaderNodeBsdfPrincipled")
        bsdf.location = (-200, 100)
        
        # 创建材质输出节点
        output = nodes.new("ShaderNodeOutputMaterial")
        output.location = (200, 100)
        
        # 创建图像纹理节点
        image_texture = nodes.new("ShaderNodeTexImage")
        image_texture.location = (-600, 100)
        
        # 加载图片
        if material_setting.image_path:
            image_name = os.path.basename(material_setting.image_path)
            if image_name in bpy.data.images:
                image = bpy.data.images[image_name]
                self.report({ 'INFO' }, f"使用图片已有图像数据: {image_name}")
            else:
                try:
                    image = bpy.data.images.load(material_setting.image_path)
                except Exception as e:
                    self.report({'ERROR'}, f"错误{str(e)},无法导入图片")
                    return False
            image_texture.image = image
        
        
        # 创建映射节点
        mapping = nodes.new("ShaderNodeMapping")
        mapping.location = (-900, 100)
        
        # 初始化映射节点位置
        mapping.inputs['Location'].default_value[0] = (
            material_setting.start_x + material_setting.columns * material_setting.offset_x
        )
        mapping.inputs['Location'].default_value[1] = (
            material_setting.start_y + material_setting.rows * material_setting.offset_y
        )
        
        # 创建纹理坐标节点
        tex_coord = nodes.new("ShaderNodeTexCoord")
        tex_coord.location = (-1200, 100)
        
        # 连接节点
        links.new(bsdf.outputs['BSDF'], output.inputs['Surface'])
        links.new(image_texture.outputs['Color'], bsdf.inputs['Base Color'])
        links.new(image_texture.outputs['Alpha'], bsdf.inputs['Alpha'])
        links.new(mapping.outputs['Vector'], image_texture.inputs['Vector'])
        links.new(tex_coord.outputs['UV'], mapping.inputs['Vector'])
        return True

# 插入关键帧操作符
class MATERIAL_OT_insert_keyframe(Operator):
    bl_idname = "material.insert_keyframe"
    bl_label = "插入关键帧"
    bl_description = "为当前材质的映射节点位置插入关键帧"
    
    def execute(self, context):
        scene_settings = context.scene.scene_settings
        
        if 0 <= scene_settings.active_index < len(scene_settings.material_settings):
            active_setting = scene_settings.material_settings[scene_settings.active_index]
            
            # 获取材质
            material = bpy.data.materials.get(active_setting.name)
            for slot in material.node_tree.nodes:
                if slot.type and slot.type == 'MAPPING':
                    slot.inputs["Location"].keyframe_insert(data_path="default_value")
                    self.report({'INFO'}, f"已为 {material.name} 的映射节点插入关键帧")
            
            # 选中映射节点以确保关键帧在时间轴可见
            self.select_mapping_node(context, active_setting.name)
        else:
            self.report({'WARNING'}, "没有选中的材质")
        
        return {'FINISHED'}
    
    def select_mapping_node(self, context, material_name):
        """选中映射节点以确保关键帧在时间轴可见"""
        if material_name in bpy.data.materials:
            mat = bpy.data.materials[material_name]
            if mat.use_nodes:
                # 查找映射节点
                mapping_node = None
                for node in mat.node_tree.nodes:
                    if node.type == 'MAPPING':
                        mapping_node = node
                        break
                
                # 选中映射节点（需要节点编辑器上下文）
                if mapping_node:
                    # 尝试切换到节点编辑器并选中节点
                    for area in context.screen.areas:
                        if area.type == 'NODE_EDITOR':
                            for space in area.spaces:
                                if space.type == 'NODE_EDITOR':
                                    space.node_tree = mat.node_tree
                                    for node in mat.node_tree.nodes:
                                        node.select = False
                                    mapping_node.select = True
                                    mat.node_tree.nodes.active = mapping_node
                                    break
                            break

# 删除关键帧操作符
class MATERIAL_OT_delete_keyframe(Operator):
    bl_idname = "material.delete_keyframe"
    bl_label = "删除关键帧"
    bl_description = "删除当前材质的映射节点位置关键帧"
    
    def execute(self, context):
        scene_settings = context.scene.scene_settings
        
        if 0 <= scene_settings.active_index < len(scene_settings.material_settings):
            active_setting = scene_settings.material_settings[scene_settings.active_index]
            
            
            # 获取材质
            material = bpy.data.materials.get(active_setting.name)
            for slot in material.node_tree.nodes:
                if slot.type and slot.type == 'MAPPING':
                    slot.inputs["Location"].keyframe_delete(data_path="default_value")
                    self.report({'INFO'}, f"已删除 {material.name} 的映射节点关键帧")

            
            # 选中映射节点以确保关键帧在时间轴可见
            self.select_mapping_node(context, active_setting.name)
            self.refresh_timeline_ui(context)
        else:
            self.report({'WARNING'}, "没有选中的材质")
        
        return {'FINISHED'}
    
    def select_mapping_node(self, context, material_name):
        """选中映射节点以确保关键帧在时间轴可见"""
        if material_name in bpy.data.materials:
            mat = bpy.data.materials[material_name]
            if mat.use_nodes:
                # 查找映射节点
                mapping_node = None
                for node in mat.node_tree.nodes:
                    if node.type == 'MAPPING':
                        mapping_node = node
                        break
                
                # 选中映射节点（需要节点编辑器上下文）
                if mapping_node:
                    # 尝试切换到节点编辑器并选中节点
                    for area in context.screen.areas:
                        if area.type == 'NODE_EDITOR':
                            for space in area.spaces:
                                if space.type == 'NODE_EDITOR':
                                    space.node_tree = mat.node_tree
                                    for node in mat.node_tree.nodes:
                                        node.select = False
                                    mapping_node.select = True
                                    mat.node_tree.nodes.active = mapping_node
                                    break
                            break

    def refresh_timeline_ui(self, context):
        """强制刷新时间轴UI"""
        # 刷新所有区域
        for area in context.screen.areas:
            # 刷新时间轴区域
            if area.type == 'TIMELINE':
                area.tag_redraw()
            # 刷新动画编辑器区域
            elif area.type in ('DOPESHEET_EDITOR', 'GRAPH_EDITOR'):
                area.tag_redraw()
        
        # 强制更新UI
        context.area.tag_redraw()
        bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)

# 刷新材质列表操作符
class MATERIAL_OT_refresh_list(Operator):
    bl_idname = "material.refresh_list"
    bl_label = "刷新列表"
    bl_description = "刷新材质列表"
    
    def execute(self, context):
        # 去除无效的材质设置
        met = context.scene.scene_settings.material_settings
        # 收集所有需要删除的项的索引
        indices_to_remove = []
        for i, me in enumerate(met):
            if me.name not in bpy.data.materials:
                indices_to_remove.append(i)
        
        # 从后往前删除，避免索引变化问题
        for i in reversed(indices_to_remove):
            met.remove(i)
        return {'FINISHED'}
    
class MATERIAL_OT_delete_material(Operator):
    bl_idname = "material.delete_material"
    bl_label = "删除材质"
    bl_description = "删除当前材质"
    
    def execute(self, context):
        try:
            material= bpy.data.materials[context.scene.scene_settings.material_settings[context.scene.scene_settings.active_index].name]
        except BaseException as e:
            self.report({'ERROR'}, f"材质不存在或已删除,{str(e)}")
            context.scene.scene_settings.material_settings.remove(context.scene.scene_settings.active_index)
            return {'CANCELLED'}
        if material and material.users <= 0:
            context.scene.scene_settings.material_settings.remove(context.scene.scene_settings.active_index)
            bpy.data.materials.remove(material)
        self.report({'INFO'}, "已删除材质")
        return {'FINISHED'}

# 面板UI
class MATERIAL_PT_position_expression(Panel):
    bl_label = "位置表情切换"
    bl_idname = "MATERIAL_PT_position_expression"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "material"
    
    def draw(self, context):
        layout = self.layout
        scene = context.scene
        scene_settings = scene.scene_settings
        
        # 主框
        box = layout.box()
        
        # 材质列表
        row = box.row()
        row.template_list(
            "MATERIAL_UL_list", 
            "", 
            scene_settings, 
            "material_settings", 
            scene_settings, 
            "active_index",
            rows=4
        )
        
        # 右侧按钮列
        col = row.column(align=True)
        col.operator("material.select_image", icon='ADD', text="")
        col.operator("material.refresh_list", icon='FILE_REFRESH', text="")
        
        # 如果有选中的材质项，显示控制选项
        if 0 <= scene_settings.active_index < len(scene_settings.material_settings):
            active_setting = scene_settings.material_settings[scene_settings.active_index]
            
            # 检查材质是否被使用
            material_used = active_setting.name in bpy.data.materials and bpy.data.materials[active_setting.name].users > 0
            
            if material_used:
                
                col.operator("material.insert_keyframe", icon='KEY_HLT', text="")
                col.operator("material.delete_keyframe", icon='KEY_DEHLT', text="")
                col.operator("material.select_material_objects", icon='RESTRICT_SELECT_OFF', text="")
                # 添加选择材质物体的按钮
                row = box.row()
                
                # 行列控制
                row = box.row()
                row.label(text="行列位置设置:")
                row = box.row(align=True)
                row.prop(active_setting, "columns", text="X")
                row.prop(active_setting, "rows", text="Y")
                
                # 偏移控制
                row = box.row()
                row.label(text="行列间距设置:")
                row = box.row(align=True)
                row.prop(active_setting, "offset_x", text="X")
                row.prop(active_setting, "offset_y", text="Y")
                
                # 起始点控制
                row = box.row()
                row.label(text="起始点设置:")
                row = box.row(align=True)
                row.prop(active_setting, "start_x", text="X")
                row.prop(active_setting, "start_y", text="Y")
                
            else:
                # 材质未被使用时显示提示信息
                col.operator("material.delete_material", icon='TRASH', text="")
                box.label(text="材质未被使用", icon='ERROR')
            # 显示材质使用信息
            usage_box = box.box()
            usage_row = usage_box.row()
            if active_setting.name in bpy.data.materials:
                mat = bpy.data.materials[active_setting.name]
                usage_row.label(text=f"材质: {active_setting.name}, 使用数: {mat.users}")
            else:
                usage_row.label(text=f"材质: {active_setting.name}, 尚未创建")

# 材质列表UI
class MATERIAL_UL_list(UIList):
    def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
        if self.layout_type in {'DEFAULT', 'COMPACT'}:
            # 检查材质是否被使用
            material_used = item.name in bpy.data.materials and bpy.data.materials[item.name].users > 0
            
            row = layout.row(align=True)
            
            # 显示材质图标或错误图标
            if material_used:
                row.label(text="", icon='MATERIAL')
            else:
                row.label(text="", icon='ERROR')
            
            # 显示材质名称
            row.label(text=item.name)
            
            # 显示激活状态图标
            scene_settings = context.scene.scene_settings
            is_active = scene_settings.active_index == self.get_index(context, item)
            
            if is_active:
                row.label(text="", icon='CHECKMARK')
            
        elif self.layout_type in {'GRID'}:
            layout.alignment = 'CENTER'
            layout.label(text="", icon='MATERIAL')

    def get_index(self, context, item):
        """获取项目在列表中的索引"""
        scene_settings = context.scene.scene_settings
        for i, setting in enumerate(scene_settings.material_settings):
            if setting == item:
                return i
        return -1

class M_OT_select_material_objects(Operator):
    bl_idname = "material.select_material_objects"
    bl_label = "选择材质物体"
    bl_description = "选择使用当前材质的物体并聚焦映射节点"
    
    def execute(self, context):
        scene_settings = context.scene.scene_settings
        
        if 0 <= scene_settings.active_index < len(scene_settings.material_settings):
            material_name = scene_settings.material_settings[scene_settings.active_index].name
            
            # 检查材质是否被使用
            material_used = material_name in bpy.data.materials and bpy.data.materials[material_name].users > 0
            
            if material_used:
                # 选择使用此材质的物体
                self.select_objects_with_material(context, material_name)
        
        return {'FINISHED'}
    
    def select_objects_with_material(self, context, material_name):
        """选择使用指定材质的物体并聚焦映射节点"""
        # 先取消所有选择
        bpy.ops.object.select_all(action='DESELECT')
        
        # 选择使用该材质的所有网格物体
        objects_with_material = []
        for obj in bpy.data.objects:
            if obj.type == 'MESH' and obj.data.materials:
                for mat_slot in obj.data.materials:
                    if mat_slot and mat_slot.name == material_name:
                        obj.select_set(True)
                        objects_with_material.append(obj)
                        break
        
        # 设置活动对象（如果有选中的物体）
        if objects_with_material:
            context.view_layer.objects.active = objects_with_material[0]
            
            # 尝试在节点编辑器中选中映射节点
            if material_name in bpy.data.materials:
                mat = bpy.data.materials[material_name]
                if mat.use_nodes:
                    # 查找映射节点
                    mapping_node = None
                    for node in mat.node_tree.nodes:
                        if node.type == 'MAPPING':
                            mapping_node = node
                            break
                    
                    # 选中映射节点（需要节点编辑器上下文）
                    if mapping_node:
                        # 尝试切换到节点编辑器并选中节点
                        for area in context.screen.areas:
                            if area.type == 'NODE_EDITOR':
                                for space in area.spaces:
                                    if space.type == 'NODE_EDITOR':
                                        space.node_tree = mat.node_tree
                                        for node in mat.node_tree.nodes:
                                            node.select = False
                                        mapping_node.select = True
                                        mat.node_tree.nodes.active = mapping_node
                                        break
                                break

# 注册类
classes = (
    MaterialSettings,
    SceneSettings,
    MATERIAL_OT_select_image,
    MATERIAL_OT_insert_keyframe,
    MATERIAL_OT_delete_keyframe,
    MATERIAL_OT_refresh_list,
    MATERIAL_PT_position_expression,
    MATERIAL_OT_delete_material,
    M_OT_select_material_objects,
    MATERIAL_UL_list,
)

def xy_register():
    for cls in classes:
        bpy.utils.register_class(cls)
    
    bpy.types.Scene.scene_settings = PointerProperty(type=SceneSettings)

def xy_unregister():
    for cls in reversed(classes):
        bpy.utils.unregister_class(cls)
    
    del bpy.types.Scene.scene_settings

# 注册插件
if __name__ == "__main__":
    
    xy_register()